package com.geodroid.common.kernel.geos;


import java.util.ArrayList;

import com.geodroid.common.kernel.Kernel;
import com.geodroid.common.kernel.Path;
import com.geodroid.common.kernel.PathParameter;
import com.geodroid.kernel.matrix.Coords;





public class GeoLine extends GeoVec3D implements Path {




	public GeoPoint2 startPoint;
	/** end point*/
	public GeoPoint2 endPoint;
	
	
	
	/** list of points on this line*/
	protected ArrayList<GeoPoint2> pointsOnLine;
	private PathParameter tempPP;
	
	/**
	 * Creates new GeoLine
	 * 
	 * @param cons construction
	 * @param label label
	 * @param a x-coefficient
	 * @param b y-coefficient
	 * @param c z-coefficient
	 */
	public GeoLine(double a, double b, double c) {
		super(a, b, c); // GeoVec3D constructor
	}
	
	
	/**
	 * 
	 */
	public GeoLine() {
		super();
		// TODO Auto-generated constructor stub
	}


	public void setCoords(double x, double y, double z) {
		this.x = x;
		this.y = y;
		this.z = z;

	}

	@Override
	public void setCoords(GeoVec3D v) {

		setCoords(v.x, v.y, v.z);
		/*
		 * x = v.x; y = v.y; z = v.z;
		 */
	}
	
	/**
	 * @param P start point
	 */
	public final void setStartPoint(GeoPoint2 P) {
		startPoint = P;
//		if (P != null)
//			P.addIncidence(this);
	}

	/**
	 * @param Q end point
	 */
	public final void setEndPoint(GeoPoint2 Q) {
		endPoint = Q;
//		if (Q != null)
//			Q.addIncidence(this);
	}

	/**
	 * Retuns first defining point of this line or null.
	 */
	final public GeoPoint2 getStartPoint() {
		return startPoint;
	}

	/**
	 * Retuns second point of this line or null.
	 */
	final public GeoPoint2 getEndPoint() {
		return endPoint;
	}


	public void pointChanged(GeoPoint2 PI) {
		// TODO Auto-generated method stub
		
	}


	public void pathChanged(GeoPoint2 PI) {
		// TODO Auto-generated method stub
		
	}


	/**
	 * States wheter P lies on this line or not.
	 * 
	 * @return true iff P lies on this line
	 * @param P
	 *            point
	 * @param eps
	 *            precision (ratio of allowed error and |x|+|y|)
	 */
	public final boolean isOnFullLine(GeoPoint2 P, double eps) {
		if (!P.isDefined())
			return false;

		double simplelength = Math.abs(x) + Math.abs(y);
		if (P.isInfinite()) {
			return Math.abs(x * P.x + y * P.y) < eps * simplelength;
		}
		// STANDARD CASE: finite point
		return Math.abs(x * P.inhomX + y * P.inhomY + z) < eps * simplelength;
	}

	public final boolean isOnFullLine(Coords Pnd, double eps) {

		Coords P = Pnd.getCoordsIn2DView();

		double simplelength = Math.abs(x) + Math.abs(y);
		if (Kernel.isZero(P.getZ())) { // infinite point
			return Math.abs(x * P.getX() + y * P.getY()) < eps * simplelength;
		}
		// STANDARD CASE: finite point
		return Math.abs(x * P.getX() / P.getZ() + y * P.getY() / P.getZ() + z) < eps
				* simplelength;
	}

	public double getMinParameter() {
		return Double.NEGATIVE_INFINITY;
	}


	public double getMaxParameter() {
		return Double.POSITIVE_INFINITY;
	}


	public boolean isClosedPath() {
		// TODO Auto-generated method stub
		return false;
	}


	

		/**
	 * Adds a point to the list of points that this line passes through.
	 * @param p point tobe added
	 */
	public final void addPointOnLine(GeoPoint2 p) {
		if (pointsOnLine == null)
			pointsOnLine = new ArrayList<GeoPoint2>();

		if (!pointsOnLine.contains(p))
			pointsOnLine.add((GeoPoint2) p);
	}
	
	
	/**
	 * Removes a point from the list of points that this line passes through.
	 * 
	 * @param p
	 *            Point to be removed
	 */
	public final void removePointOnLine(GeoPoint2 p) {
		if (pointsOnLine != null)
			pointsOnLine.remove(p);
	}

	@Override
	public boolean isGeoLine() {
		// TODO Auto-generated method stub
		return true;
	}
	/**
	 * returns true if P lies on this line
	 * 
	 * @param p
	 *            point
	 * @param eps
	 *            precision
	 * @return true if P lies on this line
	 * */
	public boolean isIntersectionPointIncident(GeoPoint2 p, double eps) {
		return isOnFullLine(p, eps);
	}
	
	/**
	 * @return temporary path parameter
	 */
	protected PathParameter getTempPathParameter() {
		if (tempPP == null)
			tempPP = new PathParameter();
		return tempPP;
	}
	
	/**
	 * Returns whether this point lies on this line, segment or ray.
	 */
	final public boolean isOnPath(GeoPoint2 PI, double eps) {

		GeoPoint2 P = (GeoPoint2) PI;

		if (P.getPath() == this) {
			return true;
		}

		// check if P lies on line first
		if (!isOnFullLine(P, eps)) {
			return false;
		}

		// for a line we are done here: the point is on the line
		// for rays and segments we need to continue
//		GeoClass classType = getGeoClassType();
//		if (classType.equals(GeoClass.LINE)) {
//			return true;
//		}

		// idea: calculate path parameter and check
		// if it is in [0, 1] for a segment or greater than 0 for a ray

		// remember the old point coordinates
		double px = P.x, py = P.y, pz = P.z;
		PathParameter tempParam = getTempPathParameter();
		PathParameter pp = P.getPathParameter();
		tempParam.set(pp);

		// make sure we use point changed for a line to get parameters on
		// the entire line when this is a segment or ray
		doPointChanged(P);

		boolean result;
//		switch (classType) {
//		case SEGMENT:
			// segment: parameter in [0,1]
			result = pp.t >= -eps && pp.t <= 1 + eps;
//			break;
//
//		case RAY:
			// ray: parameter > 0
//			result = pp.t >= -eps;
//			break;
//
//		default:
//			// line: any parameter
//			result = true;
//		}

		// restore old values
		P.x = px;
		P.y = py;
		P.z = pz;
		pp.set(tempParam);

		return result;
	}

	public boolean isOnPath(Coords Pnd, double eps) {
		Coords P2d = Pnd.getCoordsIn2DView();
		return isOnFullLine(P2d, eps);
	}
	
	
	private void doPointChanged(GeoPoint2 P) {

		Coords coords = P.getCoordsInD(2);
		PathParameter pp = P.getPathParameter();

		doPointChanged(coords, pp);

		P.setCoords2D(coords.getX(), coords.getY(), coords.getZ());
//		P.updateCoordsFrom2D(false, null);

	}

	/**
	 * @param coords changed point
	 * @param pp path parameter of P
	 */
	public void doPointChanged(Coords coords, PathParameter pp) {

		// project P on line
		double px = coords.getX() / coords.getZ();
		double py = coords.getY() / coords.getZ();
		// param of projection point on perpendicular line
		double t = -(z + x * px + y * py) / (x * x + y * y);
		// calculate projection point using perpendicular line
		px += t * x;
		py += t * y;

		coords.setX(px);
		coords.setY(py);
		coords.setZ(1);

		// set path parameter
		double spx = 0;
		double spy = 0;
		double spz = 1;
		if (startPoint != null) {
			spx = startPoint.x;
			spy = startPoint.y;
			spz = startPoint.z;
		} else {
			if (x != 0 && y != 0) {
				spx = -z * x / (x * x + y * y);
				spy = -z * y / (x * x + y * y);
			} else if (x != 0) {
				spx = -z / x;
			} else if (y != 0) {
				spy = -z / y;
			}
		}
		if (Math.abs(x) <= Math.abs(y)) {
			pp.t = (spz * px - spx) / (y * spz);
		} else {
			pp.t = (spy - spz * py) / (x * spz);
		}

	}
}
